import { DbContext } from '~/core/db/DbContext';
import { Configuration } from '~/core/Configuration/index';
import { EngineOptions, StyleType } from '~/core/Options';
import { CompleteQuery, Query, Queryable, QueryDescriptor, QueryMeta } from '~/core/dsl/fluent/QueryDescriptor';
import { ConsolePrintStream, createPrintStream, PrintStream } from '~/core/extensions/IOExt';
import { createResultFormatter, DefaultResultFormatter, IResultFormatter } from '~/core/extensions/ResultFormatter';
import { QueryResult } from './Defines';

/**
 * The execution engine of QVoG that runs queries and outputs results.
 *
 * @category Engine
 */
export class QVoGEngine {
    private log = Configuration.getLogger('QVoGEngine');

    // Query output style.
    private style: StyleType = 'markdown';
    // Engine execution result formatter.
    private formatter: IResultFormatter = new DefaultResultFormatter();

    private output: PrintStream = new ConsolePrintStream();

    private totalExecutionTime: number = 0;

    public constructor(config: EngineOptions) {
        this.log.info('Initializing QVoG Engine');
        Configuration.setDbContext(new DbContext(config.database));
        if (config.formatter) {
            this.withFormatter(createResultFormatter(config.formatter));
        }
        if (config.style) {
            this.withStyle(config.style);
        }
        if (config.output) {
            this.withOutput(createPrintStream(config.output, false));
        }

        this.log.info('QVoG Engine initialized');
    }

    // ---------------------------------------------------------------
    // Configuration
    // ---------------------------------------------------------------

    /**
     * Set the result formatter for the engine.
     *
     * @param formatter Result formatter, see {@link IResultFormatter | `IResultFormatter`}.
     * @returns Itself for chaining.
     */
    withFormatter(formatter: IResultFormatter): QVoGEngine {
        this.formatter = formatter;
        return this;
    }

    /**
     * Set the query output style, {@link TablePrettifier | `TablePrettifier`} for supported styles.
     *
     * @param style The style of the output.
     * @returns Itself for chaining.
     */
    withStyle(style: string | StyleType): QVoGEngine {
        this.style = style as StyleType;
        return this;
    }

    /**
     * Set the output stream for the engine.
     *
     * It provides a flexible way to output result to different destinations,
     * such as console, file, etc.
     *
     * @param output Output stream.
     * @returns Itself for chaining.
     */
    withOutput(output: PrintStream): QVoGEngine {
        this.output.close();
        this.output = output;
        return this;
    }

    /**
     * Execute a query synchronously.
     *
     * @param query Query.
     * @returns Itself for chaining.
     */
    async execute(query: Queryable): Promise<QVoGEngine> {
        await this.executeImpl(query[0], query[1]);
        return this;
    }

    /**
     * Execute a list of queries one-by-one synchronously.
     *
     * @param queries Queries.
     * @returns Itself for chaining.
     */
    async submit(queries: Queryable[]): Promise<QVoGEngine> {
        // TODO support concurrency
        // 此处不可用async for-each 否则会导致eventloop错位从而无法执行数据
        // TODO SMT聚合多个查询的条件，同条件仅查询一次
        for (const query of queries) {
            await this.execute(query)
        }
        // await Promise.all(queries.map(q => this.execute(q)))
        return this;
    }

    /**
     * Close the engine and release resources.
     */
    close(): void {
        this.log.info(`Total execution time: ${this.totalExecutionTime}ms`);
        this.output.println(`Total execution time: ${this.totalExecutionTime}ms`);
        this.output.close();
        Configuration.getDbContext().close();
        this.log.info('QVoG Engine closed');
    }

    private async executeImpl(meta: QueryMeta, query: Query): Promise<void> {
        this.log.debug(`Executing query ${meta}`);
        const start = Date.now();
        let query_ins: QueryDescriptor;
        let result = '';
        try {
            query_ins = query(new QueryDescriptor().withDatabase(Configuration.getDbContext())) as QueryDescriptor;
            await query_ins.exec()
            if (query_ins.result.getSize()) {
                result = query_ins.toString(this.style);
            }
        } catch (error) {
            this.log.warn(`Error executing query "${meta}"`, error);
            result = `Error executing query "${meta}": ${error}`;
        }
        const end = Date.now();
        const executionTime = end - start;
        this.log.debug(`Query "${meta}" executed in ${executionTime}ms`);
        result && this.report({
            name: meta.name,
            result: result,
            milliseconds: executionTime,
        })
        this.totalExecutionTime += executionTime;
    }

    public report(report: QueryResult): void {
        const content = this.formatter.format(report);
        this.output.println(content);
    }
}
